/* Copyright 2013 (C) FatalEror / Dean Bahari
 * 
 * jroCK is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * See LICENSE file in main folder for more information.
 */

package jrock.filesystem;

import static org.lwjgl.opengl.GL11.*;

import java.io.ByteArrayInputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.util.ArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;
import jrock.other.Camera;
import jrock.other.Utility;
import org.lwjgl.BufferUtils;
import org.lwjgl.util.vector.Matrix4f;
import org.lwjgl.util.vector.Vector3f;
import org.newdawn.slick.opengl.Texture;
import org.newdawn.slick.opengl.TextureLoader;

/**
 * @author FatalEror
 */
public class RSW {
    
    private final String magicKey    = "GRSW";
    private int          version     = 0;
    private GND          ground      = null;
    private Texture[]    waterTex    = null;
    private Water        water       = new Water();
    
    private ArrayList<Model> modelList = new ArrayList<>();
    private ArrayList<RSM>   modelData = new ArrayList<>();
    // private ArrayList<Light>   lsource = new ArrayList<>();
    // private ArrayList<Sound>   sound   = new ArrayList<>();
    // private ArrayList<Effect>  effect  = new ArrayList<>();
    
    public class Water {
        private float     level    = 0.0f;
        private int       type     = 0;
        private float     height   = 1.0f;
        private float     speed    = 2.0f;
        private float     pitch    = 50.0f;
        private int       aniSpeed = 3;
        private Texture[] texture  = null;
    }
    
    private class Model {
        private int         ID        = 0;
        private String      fileName  = "";
        private FloatBuffer transform = BufferUtils.createFloatBuffer(16);
        private Vector3f[]  pt        = new Vector3f[8];
        private boolean     checked   = false;
    }
    
    private class Light {
    }
    
    private class Sound {
    }
    
    private class Effect {
    }
    
    private Vector3f multMatrix(FloatBuffer matrix, Vector3f v) {
        Vector3f vout = new Vector3f();
        vout.x = v.x * matrix.get(0) + v.y * matrix.get(4) + v.z * matrix.get(8) + matrix.get(12);
        vout.y = v.x * matrix.get(1) + v.y * matrix.get(5) + v.z * matrix.get(9) + matrix.get(13);
        vout.z = v.x * matrix.get(2) + v.y * matrix.get(6) + v.z * matrix.get(10) + matrix.get(14);
        
        return vout;
    }
    
    private float phase = 0.0f;
    private int frame = 0;
    public void draw(Camera cam) {
        ground.draw();
        for (int i = 0; i < modelList.size(); i++) {
            Model tmpModel = modelList.get(i);
            RSM   tmpData  = modelData.get(tmpModel.ID);
            
            if (!tmpModel.checked) {
                Vector3f max = tmpData.getBBMax();
                Vector3f min = tmpData.getBBMin();
                tmpModel.pt[0] = new Vector3f(min.x, min.y, min.z);
                tmpModel.pt[1] = new Vector3f(min.x, max.y, min.z);
                tmpModel.pt[2] = new Vector3f(min.x, min.y, max.z);
                tmpModel.pt[3] = new Vector3f(min.x, max.y, max.z);
                tmpModel.pt[4] = new Vector3f(max.x, min.y, min.z);
                tmpModel.pt[5] = new Vector3f(max.x, max.y, min.z);
                tmpModel.pt[6] = new Vector3f(max.x, min.y, max.z);
                tmpModel.pt[7] = new Vector3f(max.x, max.y, max.z);
                
                for (int j = 0; j < 8; j++) {
                    tmpModel.pt[j] = multMatrix(tmpModel.transform, tmpModel.pt[j]);
                    tmpModel.pt[j] = multMatrix(cam.getTransform(), tmpModel.pt[j]);
                }
                
                tmpModel.checked = true;
            }
            
            boolean inside = false;
            for (int j = 0; j < 8; j++) {
                if (cam.pointInFrustum(tmpModel.pt[j])) {
                    inside = true;
                    break;
                }
            }
            if (!inside) {
                continue;
            }

            glPushMatrix();
            glMultMatrix(tmpModel.transform);
            tmpData.draw();
            glPopMatrix();
        }
        
        frame++;
        if (frame > 31) {
            frame = 0;
        }
            
        phase += 10.0f;
        if (phase >= 360.0f) {
            phase = 0.0f;
        }
            
        glPushMatrix();
        glColor4f(1.0f, 1.0f, 1.0f, 0.4f);
        glTranslatef(-ground.getWidth() * ground.getScale() / 2.0f, water.level, -ground.getHeight() * ground.getScale() / 2.0f);
        for (int y = 0; y < ground.getHeight(); y++) {
            for (int x = 0; x < ground.getWidth(); x++) {
                double h1, h2, h3, h4;
                h1 = Math.sin(Math.toRadians(x * 5 + phase)) + Math.sin(Math.toRadians(y * 7 + phase));
                h2 = Math.sin(Math.toRadians(x * 5 + phase)) + Math.sin(Math.toRadians((y + 1) * 7 + phase));
                h3 = Math.sin(Math.toRadians((x + 1) * 5 + phase)) + Math.sin(Math.toRadians((y + 1) * 7 + phase));
                h4 = Math.sin(Math.toRadians((x + 1) * 5 + phase)) + Math.sin(Math.toRadians(y * 7 + phase));
                   
                float t1, t2, s1, s2;
                t1 = (x % 5) / 5.0f;
                t2 = ((x % 5) + 1) / 5.0f;
                s1 = (y % 5) / 5.0f;
                s2 = ((y % 5) + 1) / 5.0f;
                    
                waterTex[frame].bind();
                glBegin(GL_TRIANGLES);
                glTexCoord2f(t1, s1);
                glVertex3f(x * ground.getScale(), (float)h1 * water.height, y * ground.getScale());
                glTexCoord2f(t1, s2);
                glVertex3f(x * ground.getScale(), (float)h2 * water.height, (y + 1) * ground.getScale());
                glTexCoord2f(t2, s2);
                glVertex3f((x + 1) * ground.getScale(), (float)h3 * water.height, (y + 1) * ground.getScale());
                    
                glTexCoord2f(t1, s1);
                glVertex3f(x * ground.getScale(), (float)h1 * water.height, y * ground.getScale());
                glTexCoord2f(t2, s1);
                glVertex3f((x + 1) * ground.getScale(), (float)h4 * water.height, y * ground.getScale());
                glTexCoord2f(t2, s2);
                glVertex3f((x + 1) * ground.getScale(), (float)h3 * water.height, (y + 1) * ground.getScale());
                glEnd();
            }
        }
        glPopMatrix();
    }
    
    public boolean read(GRF grf, String fileName) {
        try {
            ByteBuffer bb = grf.getData("data\\" + fileName);
            bb.order(ByteOrder.LITTLE_ENDIAN);
           
            // Check if input file is a valid RSW file by its signature/magic key
            byte[] tmpData = new byte[magicKey.length()];
            bb.get(tmpData, 0, magicKey.length());
            if (!magicKey.contentEquals(new String(tmpData))) {
                throw new Exception("Error: " + fileName + " is not a valid RSW file.");
            }
           
            version = bb.get() * 10 + bb.get();
            
            // Fetch 4 files
            bb.position(bb.position() + 40); // Unused
            
            ground = grf.getGND(Utility.read(bb, 0, 40));
            
            bb.position(bb.position() + 40); // GAT file, unused yet
            bb.position(bb.position() + 40); // Unknown file, unused
            
            // Water properties, for version 13 and above
            if (version >= 13) {
                water.level = bb.getFloat();
                if (version >= 18) {
                    water.type   = bb.getInt();
                    water.height = bb.getFloat();
                    water.speed  = bb.getFloat();
                    water.pitch  = bb.getFloat();
                    if (version >= 19) {
                        water.aniSpeed = bb.getInt();
                    }
                }
            }
            
            // Load water textures
            waterTex = new Texture[32];
            for (int i = 0; i < 32; i++) {
                waterTex[i] = TextureLoader.getTexture("JPG", new ByteArrayInputStream(grf.getData("data\\texture\\¿öÅÍ\\water" + water.type + (i < 10 ? "0" : "") + i + ".jpg").array()));
            }
            
            // Light
            bb.position(bb.position() + 32);
            
            // Unknown
            bb.position(bb.position() + 4);
            
            // Ground
            int top = bb.getInt();
            int bottom = bb.getInt();
            int left = bb.getInt();
            int right = bb.getInt();
            
            int countObject = bb.getInt();
            for (int i = 0; i < countObject; i++) {
                switch (bb.getInt()) {
                    case 1: // Model
                    {
                        Model tmpModel = new Model();
                        
                        bb.position(bb.position() + 52);
                        
                        tmpModel.fileName = Utility.read(bb, 0, 80);
                        
                        boolean isExist = false;
                        for (int j = 0; j < modelList.size(); j++) {
                            if (tmpModel.fileName.equals(modelList.get(j).fileName)) {
                                tmpModel.ID = modelList.get(j).ID;
                                isExist = true;
                                break;
                            }
                        }
                        if (!isExist) {
                            tmpModel.ID = modelData.size();
                            RSM rsm = grf.getRSM(tmpModel.fileName);
                            modelData.add(rsm);
                        }
                        
                        bb.position(bb.position() + 80);
                        
                        Matrix4f mx = new Matrix4f();
                        mx.setIdentity();
                        mx.translate(new Vector3f(bb.getFloat(), bb.getFloat(), bb.getFloat()));
                        mx.rotate((float)Math.toRadians(bb.getFloat()), new Vector3f(1.0f, 0.0f, 0.0f));
                        mx.rotate((float)Math.toRadians(bb.getFloat()), new Vector3f(0.0f, 1.0f, 0.0f));
                        mx.rotate((float)Math.toRadians(bb.getFloat()), new Vector3f(0.0f, 0.0f, 1.0f));
                        mx.scale(new Vector3f(bb.getFloat(), bb.getFloat(), bb.getFloat()));
                        mx.store(tmpModel.transform);
                        tmpModel.transform.flip();

                        modelList.add(tmpModel);
                    } break;
                    case 2: // Light
                    {
                        bb.position(bb.position() + 80 + 12 + 4 + 4 + 4 + 4);
                    } break;
                    case 3: // Sound
                    {
                        bb.position(bb.position() + 80 + 80 + 12 + 4 + 4 + 4 + 4 + 4);
                    } break;
                    case 4: // Effect
                    {
                        bb.position(bb.position() + 80 + 12 + 4 + 4 + 16);
                    } break;
                    default:
                        throw new Exception("Error: Unknown type in " + fileName);
                }
            }
            
            // Rest of buffer
            
        } catch (Exception ex) {
            Logger.getLogger(RSW.class.getName()).log(Level.SEVERE, null, ex);
            return false;
        }
        
        return true;
    }
    
    public void destroy() {
        ground.destroy();
        for (int i = 0; i < modelData.size(); i++) {
            modelData.get(i).destroy();
        }
    }
}
